package lumis.portal.channel.serialization;

import lumis.portal.PortalException;
import lumis.portal.PortalRequestParameters;
import lumis.portal.UnexpectedException;
import lumis.portal.authentication.SessionConfig;
import lumis.portal.channel.ChannelConfig;
import lumis.portal.channel.ChannelPrefixInUseException;
import lumis.portal.channel.template.serialization.ChannelTemplateLinkSerializableConfig;
import lumis.portal.channel.template.serialization.ChannelTemplateSerializationMessageConfig;
import lumis.portal.dao.DaoFactory;
import lumis.portal.group.GroupConfig;
import lumis.portal.group.serialization.GroupSerializableConfig;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.manager.ManagerFactoryInternal;
import lumis.portal.metatag.IMetaTagManager;
import lumis.portal.metatag.MetaTagConfig;
import lumis.portal.page.PageConfig;
import lumis.portal.page.serialization.PageSerializableConfig;
import lumis.portal.progress.ProgressConfig.Type;
import lumis.portal.serialization.BaseSerializationContext;
import lumis.portal.serialization.Dependency;
import lumis.portal.serialization.DeserializationContext;
import lumis.portal.serialization.SerializationContext;
import lumis.portal.serialization.SerializationMessageConfigNode;
import lumis.portal.serialization.SerializationSettings;
import lumis.portal.serialization.operation.ConfigDeletion;
import lumis.portal.serialization.operation.ConfigDependencyAnalyzer;
import lumis.portal.serialization.operation.ConfigForeignKey;
import lumis.portal.serialization.persistency.IDirectory;
import lumis.portal.serialization.persistency.sub.SubDirectory;
import lumis.portal.serviceinstance.serialization.ServiceInstanceSerializableConfig;
import lumis.portal.user.UserConfig;
import lumis.portal.user.serialization.UserSerializableConfig;
import lumis.util.ITransaction;
import lumis.util.PortalUtil;
import lumis.util.TextUtil;
import lumis.util.XmlUtil;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

/**
 * Manager for Channel serialization
 *
 * @version $Revision: 12820 $ $Date: 2011-04-15 11:16:02 -0300 (Fri, 15 Apr 2011) $
 * @since 4.0.7
 */
public class ChannelSerializationManager implements IChannelSerializationManager
{
	private final String[] primaryKeyFieldNames = new String[] {"id"};
	protected final List<ConfigForeignKey> foreignKeys = Arrays.asList(new ConfigForeignKey[] {
			new ConfigForeignKey("parentId"),
			new ConfigForeignKey("accessControlListId"),
			new ConfigForeignKey("propertyBagId"),
			new ConfigForeignKey("pageTemplateId", null, true, false, true, ManagerFactory.getPageManager()),
			new ConfigForeignKey("parentTemplateId", null, true, false, true, ManagerFactory.getChannelManager())
	});

	private final String configAlias = "channel";

	// DESERIALIZATION

	public ChannelConfig deserializeConfig(DeserializationContext deserializationContext, String channelId) throws PortalException
	{
		deserializationContext.logDebug("Deserializing config");
		ChannelConfig channelConfig = ManagerFactoryInternal.getSerializationManager2().deserializeObject(ChannelConfig.class, deserializationContext.getDirectory(), getConfigPath(deserializationContext, channelId), configAlias, false);
		// replace old portal channel Id by the new value
		if ("00000000D00000000000000000000001".equals(channelConfig.getParentId()))
			channelConfig.setParentId(ChannelConfig.PORTAL_CHANNEL_ID);

		try
		{
			channelConfig.isAutomaticFriendlyId();
		}
		catch (NullPointerException e)
		{
			// the automatic friendly id wasn't found
			channelConfig.setAutomaticFriendlyId(true);
		}

		// if friendly id wasn't deserialized, create an automatic one (for
		// compatibility reason)
		if (channelConfig.getFriendlyId() == null && channelConfig.isAutomaticFriendlyId())
		{
			channelConfig.setAutoGeneratedFriendlyId();
		}

		if (deserializationContext.isCreateNewIds() && channelConfig.hasLocalGroups())
		{
			String originalPrefix = channelConfig.getLocalGroupPrefix();
			String newPrefix = deserializationContext.getLocalGroupPrefixMapping(originalPrefix);
			if (newPrefix == null)
			{
				newPrefix = DaoFactory.getChannelDao().generateRandomChannelPrefix(deserializationContext.getTransaction());
				deserializationContext.addLocalGroupPrefixMapping(originalPrefix, newPrefix);
			}
			channelConfig.setLocalGroupPrefix(newPrefix);
			channelConfig.setTemplateLocalGroupPrefix(newPrefix);
		}

		return channelConfig;
	}

	public String deserialize(DeserializationContext deserializationContext, ChannelSerializableConfig channelSerializableConfig, ChannelSerializableConfig parentChannelSerializableConfig) throws PortalException
	{
		deserializationContext.logDebug("Deserializing channel '" + channelSerializableConfig.getId() + "'");

		String channelFullName = (parentChannelSerializableConfig == null ? "" : parentChannelSerializableConfig.getName()) + " / " + channelSerializableConfig.getName();
		deserializationContext.setCurrentChannelPath(channelFullName);

		// initialize progress
		deserializationContext.setItemProgressMax(getProgressDeserializationItemCount() + channelSerializableConfig.getPages().size());
		deserializationContext.setItemProgressValue(0);

		// mark all current subchannels as candidates for deletion and unmark current channel
		if (!deserializationContext.isImportAsChild())
		{
			deserializationContext.getConfigDeletions().remove(channelSerializableConfig.getId());

			if(!deserializationContext.isShallowSerialization())
			{
				for (String subChannelId : ManagerFactory.getChannelManager().getIdsByParentChannelId(deserializationContext.getSessionConfig(), channelSerializableConfig.getId(), deserializationContext.getTransaction()))
					deserializationContext.addConfigDeletion(new ConfigDeletion(subChannelId, this));
			}
		}

		// deserialize config
		deserializationContext.updateProgressBar(1, 1, "STR_IMPORTING;" + channelFullName);
		ChannelConfig channelConfig = deserializeConfig(deserializationContext, channelSerializableConfig.getId());

		// apply primary key id mappings
		deserializationContext.applyPrimaryKeyFieldIdMappings(channelConfig, primaryKeyFieldNames);

		if(parentChannelSerializableConfig == null)
			deserializationContext.setRootChannelId(deserializationContext.getIdMapping(channelConfig.getId()));

		// deserialize access control list
		deserializationContext.updateProgressBar(1, 0, "STR_IMPORTING_ACL;" + channelFullName);
		ManagerFactoryInternal.getChannelAclSerializationManager().deserialize(deserializationContext, channelSerializableConfig.getAccessControlList());

		// deserialize property bag
		deserializationContext.updateProgressBar(1, 0, "STR_IMPORTING_PROPERTY_BAG;" + channelFullName);
		String parentChannelId = deserializationContext.getIdMapping(channelConfig.getParentId());
		ChannelConfig parentChannelConfig = ManagerFactory.getChannelManager().get(deserializationContext.getSessionConfig(), parentChannelId, deserializationContext.getTransaction());
		ManagerFactoryInternal.getPropertyBagSerializationManager().deserializePropertyBag(deserializationContext, channelConfig.getPropertyBagId(), parentChannelConfig.getPropertyBagId());

		// if the channel is the root && is create new ids && channel is
		// automatic friendly id then create new one
		if (parentChannelSerializableConfig == null && deserializationContext.isCreateNewIds()
				&& channelConfig.isAutomaticFriendlyId())
		{
			channelConfig.setAutoGeneratedFriendlyId();
		}

		// if root, verify the friendly id
		if (parentChannelSerializableConfig == null)
		{
			ChannelConfig clone;
			try
			{
				clone = channelConfig.clone();
			}
			catch (CloneNotSupportedException e)
			{
				throw new UnexpectedException(e);
			}
			deserializationContext.applyForeignKeyFieldIdMappings(clone, foreignKeys);

			ManagerFactoryInternal.getChannelManager().validateFriendlyId(
					deserializationContext.getSessionConfig(), clone,
					deserializationContext.getTransaction());
		}

		// analyze dependencies and persist config
		deserializationContext.updateProgressBar(1, 1, "STR_PERSISTING;" + channelFullName);
		ConfigDependencyAnalyzer<?> dependencyAnalyzer = new ConfigDependencyAnalyzer<ChannelConfig>(deserializationContext, foreignKeys, channelConfig);
		dependencyAnalyzer.persist(this, true);

		// persist dependent configs
		deserializationContext.updateProgressBar(1, 1, "STR_PERSISTING_DEPENDENT_OBJECTS;" + channelFullName);
		deserializationContext.persistDependentConfigs(channelSerializableConfig.getId());

		// deserialize metaTags
		deserializeMetaTags(deserializationContext, channelSerializableConfig);

		// deserialize service instances
		deserializationContext.updateProgressBar(1, 1, "STR_IMPORTING_SERVICE_INSTANCES;" + channelFullName);
		deserializeServiceInstances(deserializationContext, channelSerializableConfig);

		// deserialize local users
		// deserializationContext.updateProgressBar(1, 1, "STR_IMPORTING_LOCAL_USERS;" + channelFullName);
		// deserializeLocalUsers(deserializationContext, channelSerializableConfig);

		// deserialize local groups
		deserializationContext.updateProgressBar(1, 1, "STR_IMPORTING_LOCAL_GROUPS;" + channelFullName);
		deserializeLocalGroups(deserializationContext, channelSerializableConfig);

		// deserialize channel css
		deserializationContext.updateProgressBar(1, 1, "STR_IMPORTING_CSS_LINKS;" + channelFullName);
		deserializeCsss(deserializationContext, channelSerializableConfig, channelConfig);

		// deserialize pages - is necessary to backup original deserialization root path, because pages have their own root paths
		deserializationContext.updateProgressBar(1, 1, "STR_IMPORTING_PAGES;" + channelFullName);
		deserializePages(deserializationContext, channelSerializableConfig);

		// deserialize channel template links
		deserializationContext.updateProgressBar(1, 1, "STR_IMPORTING_CHANNEL_TEMPLATE_LINKS;" + channelFullName);
		for (ChannelTemplateLinkSerializableConfig channelTemplateLinkSerializableConfig : channelSerializableConfig.getChannelTemplateLinks())
			ManagerFactoryInternal.getChannelTemplateSerializationManager().deserializeLink(deserializationContext, channelTemplateLinkSerializableConfig);

		// serialize channel again to map any internal referencing templates
		boolean prevValue = deserializationContext.isUpdateIfElementExists();
		try
		{
			Field field = DeserializationContext.class.getDeclaredField("updateIfElementExists");
			field.setAccessible(true);
			if(!prevValue)
				field.set(deserializationContext, true);
			dependencyAnalyzer.persist(this, false);
			if(!prevValue)
				field.set(deserializationContext, prevValue);
		}
		catch(PortalException e)
		{
			throw e;
		}
		catch(Exception e)
		{
			throw new UnexpectedException(e);
		}

		if(!deserializationContext.isShallowSerialization())
		{
			// deserialize subchannels
			for (ChannelSerializableConfig subchannelSerializableConfig : channelSerializableConfig.getChannels())
				deserialize(deserializationContext, subchannelSerializableConfig, channelSerializableConfig);
		}

		return channelConfig.getId();
	}

	public void deserializeContent(DeserializationContext deserializationContext, ChannelSerializableConfig channelSerializableConfig, ChannelSerializableConfig parentChannelSerializableConfig) throws PortalException
	{
		deserializationContext.logDebug("Deserializing channel content'" + channelSerializableConfig.getId() + "'");

		String channelFullName = (parentChannelSerializableConfig == null ? "" : parentChannelSerializableConfig.getName()) + " / " + channelSerializableConfig.getName();
		deserializationContext.setCurrentChannelPath(channelFullName);

		// initialize progress
		deserializationContext.setItemProgressMax(channelSerializableConfig.getServiceInstances().size());
		deserializationContext.setItemProgressValue(0);

		// deserialize service instances
		deserializeServiceInstancesContent(deserializationContext, channelSerializableConfig);

		// deserialize subchannels
		for (ChannelSerializableConfig subchannelSerializableConfig : channelSerializableConfig.getChannels())
			deserializeContent(deserializationContext, subchannelSerializableConfig, channelSerializableConfig);
	}

	private void deserializeServiceInstances(DeserializationContext deserializationContext, ChannelSerializableConfig channelSerializableConfig) throws PortalException
	{
		Collection<String> oldServiceInstanceIds = null;
		if (!deserializationContext.isImportAsChild())
			oldServiceInstanceIds = ManagerFactory.getServiceInstanceManager().getIdsByChannelId(deserializationContext.getSessionConfig(), channelSerializableConfig.getId(), deserializationContext.getTransaction());
		for (ServiceInstanceSerializableConfig serviceInstanceSerializableConfig : channelSerializableConfig.getServiceInstances())
		{
			ManagerFactoryInternal.getServiceInstanceSerializationManager().deserialize(deserializationContext, serviceInstanceSerializableConfig);
			if (!deserializationContext.isImportAsChild())
			{
				oldServiceInstanceIds.remove(serviceInstanceSerializableConfig.getId());
				deserializationContext.removeConfigDeletion(serviceInstanceSerializableConfig.getId());
			}
		}
		if (!deserializationContext.isImportAsChild())
			for (String oldServiceInstanceId : oldServiceInstanceIds)
				deserializationContext.addConfigDeletion(new ConfigDeletion(oldServiceInstanceId, ManagerFactory.getServiceInstanceManager()));
	}

	private void deserializeServiceInstancesContent(DeserializationContext deserializationContext, ChannelSerializableConfig channelSerializableConfig) throws PortalException
	{
		for (ServiceInstanceSerializableConfig serviceInstanceSerializableConfig : channelSerializableConfig.getServiceInstances())
		{
			deserializationContext.updateProgressBar(0, 1, "STR_IMPORTING_SERVICE_INSTANCE_CONTENT;" +
					TextUtil.escapeLocalizationParameter(deserializationContext.getCurrentChannelPath() +
					" / " + serviceInstanceSerializableConfig.getName()));
			ManagerFactoryInternal.getServiceInstanceSerializationManager().deserializeContent(deserializationContext, serviceInstanceSerializableConfig);
		}
	}

	private void deserializeLocalUsers(DeserializationContext deserializationContext, ChannelSerializableConfig channelSerializableConfig) throws PortalException
	{
		Collection<String> oldLocalUserIds = null;
		if (!deserializationContext.isImportAsChild())
		{
			oldLocalUserIds = new ArrayList<String>();
			for (UserConfig userConfig : ManagerFactory.getUserManager().getByChannelId(deserializationContext.getSessionConfig(), channelSerializableConfig.getId(), deserializationContext.getTransaction()))
				oldLocalUserIds.add(userConfig.getId());
		}
		for (UserSerializableConfig userSerializableConfig : channelSerializableConfig.getUsers())
		{
			ManagerFactoryInternal.getUserSerializationManager().deserialize(deserializationContext, userSerializableConfig);
			if (!deserializationContext.isImportAsChild())
			{
				oldLocalUserIds.remove(userSerializableConfig.getId());
				deserializationContext.removeConfigDeletion(userSerializableConfig.getId());
			}
		}
		if (!deserializationContext.isImportAsChild())
			for (String oldLocalUserId : oldLocalUserIds)
				deserializationContext.addConfigDeletion(new ConfigDeletion(oldLocalUserId, ManagerFactory.getUserManager()));
	}

	private void deserializeLocalGroups(DeserializationContext deserializationContext, ChannelSerializableConfig channelSerializableConfig) throws PortalException
	{
		Collection<String> oldLocalGroupIds = null;
		if (!deserializationContext.isImportAsChild())
		{
			oldLocalGroupIds = new ArrayList<String>();
			for (GroupConfig groupConfig : ManagerFactory.getGroupManager().getByChannelId(deserializationContext.getSessionConfig(), channelSerializableConfig.getId(), deserializationContext.getTransaction()))
					oldLocalGroupIds.add(groupConfig.getId());
		}
		for (GroupSerializableConfig groupSerializableConfig : channelSerializableConfig.getGroups())
		{
			ManagerFactoryInternal.getGroupSerializationManager().deserialize(deserializationContext, groupSerializableConfig);
			if (!deserializationContext.isImportAsChild())
			{
				oldLocalGroupIds.remove(groupSerializableConfig.getId());
				deserializationContext.removeConfigDeletion(groupSerializableConfig.getId());
			}
		}
		if (!deserializationContext.isImportAsChild())
			for (String oldLocalGroupId : oldLocalGroupIds)
				deserializationContext.addConfigDeletion(new ConfigDeletion(oldLocalGroupId, ManagerFactory.getGroupManager()));
	}

	/**
	 * Deserialize the metaTags for a channel.
	 * @param deserializationContext the deserialization context.
	 * @param channelSerializableConfig the channel serialization data.
	 * @since 4.2.0
	 */
	private void deserializeMetaTags(DeserializationContext deserializationContext,
			ChannelSerializableConfig channelSerializableConfig) throws PortalException
	{
		ITransaction transaction = deserializationContext.getTransaction();
		SessionConfig sessionConfig = deserializationContext.getSessionConfig();
		IMetaTagManager metaTagManager = ManagerFactoryInternal.getMetaTagManager();

		String channelId = deserializationContext.getIdMapping(channelSerializableConfig.getId());

		// delete existing metaTags for the channel
		List<String> metaTagIds = metaTagManager.getIdsByParentId(sessionConfig,
				channelId, transaction);
		for (String metaTagId: metaTagIds)
			metaTagManager.delete(sessionConfig, metaTagId, transaction);

		// add deserialized metaTags to the channel
		for (MetaTagConfig metaTagConfig: channelSerializableConfig.getMetaTags())
		{
			deserializationContext.applyPrimaryKeyFieldIdMappings(metaTagConfig, new String[] {"id"});
			deserializationContext.applyForeignKeyFieldIdMappings(metaTagConfig, new String[] {"parentId"});
			metaTagManager.add(sessionConfig, metaTagConfig, transaction);
		}
	}

	private void deserializeCsss(DeserializationContext deserializationContext, ChannelSerializableConfig channelSerializableConfig, ChannelConfig channelConfig) throws PortalException
	{
		Collection<String> oldCssIds = null;
		if (!deserializationContext.isImportAsChild())
			oldCssIds = ManagerFactory.getChannelManager().getCssIds(deserializationContext.getSessionConfig(), channelSerializableConfig.getId(), deserializationContext.getTransaction());
		for (int i = 0; i < channelSerializableConfig.getCsss().size(); i++)
		{
			String cssId = channelSerializableConfig.getCsss().get(i);
			if (cssId != null)
			{
				boolean addCssLink = false;
				if (!deserializationContext.isImportAsChild()) // add the CSS link only if it does not already exist
					addCssLink = !oldCssIds.remove(cssId);
				else
					addCssLink = true;

				if (addCssLink)
					ManagerFactory.getChannelManager().addCssLink(deserializationContext.getSessionConfig(), channelConfig.getId(), cssId, i, deserializationContext.getTransaction());
			}
		}

		if (!deserializationContext.isImportAsChild())
			for (String oldCssId : oldCssIds)
				ManagerFactory.getChannelManager().deleteCssLink(deserializationContext.getSessionConfig(), channelConfig.getId(), oldCssId, deserializationContext.getTransaction());
	}

	private void deserializePages(DeserializationContext deserializationContext, ChannelSerializableConfig channelSerializableConfig) throws PortalException
	{
		// backup original directory
		IDirectory originalDirectory = deserializationContext.getDirectory();

		Collection<String> oldPageIds = null;
		if (!deserializationContext.isImportAsChild())
			oldPageIds = ManagerFactory.getPageManager().getIdsByParentChannelId(deserializationContext.getSessionConfig(), channelSerializableConfig.getId(), deserializationContext.getTransaction());

		for (PageSerializableConfig pageSerializableConfig : channelSerializableConfig.getPages())
		{
			// deserialize page from its own structure
			deserializationContext.setDirectory(new SubDirectory(originalDirectory, "pages/" + pageSerializableConfig.getId()));

			// deserialize page
			ManagerFactoryInternal.getSerializationManager2().deserializePage(deserializationContext, pageSerializableConfig.getId(), false, false);

			if (!deserializationContext.isImportAsChild())
			{
				oldPageIds.remove(pageSerializableConfig.getId());
				deserializationContext.removeConfigDeletion(pageSerializableConfig.getId());
			}
		}

		if (!deserializationContext.isImportAsChild())
			for (String oldCssId : oldPageIds)
				deserializationContext.addConfigDeletion(new ConfigDeletion(oldCssId, ManagerFactory.getPageManager()));

		// restore original deserialization rootPath
		deserializationContext.setDirectory(originalDirectory);
	}

	public boolean persistConfig(DeserializationContext deserializationContext, Object config, boolean firstPersistence) throws PortalException
	{
		boolean persisted = false;

		ChannelConfig channelConfig = (ChannelConfig)config;

		// apply foreign key id mappings and analyze dependencies
		ConfigDependencyAnalyzer<ChannelConfig> dependencyAnalyzer = new ConfigDependencyAnalyzer<ChannelConfig>(deserializationContext, foreignKeys, channelConfig);
		if (dependencyAnalyzer.foreignKeysExists())
		{
			deserializationContext.logDebug("Persisting channel config:", channelConfig);
			try
			{
				// add or update config
				if (deserializationContext.isUpdateIfElementExists() || !firstPersistence)
					ManagerFactory.getChannelManager().addOrUpdate(deserializationContext.getSessionConfig(), dependencyAnalyzer.getMappedPersistableConfig(), deserializationContext.getTransaction());
				else
					ManagerFactory.getChannelManager().add(deserializationContext.getSessionConfig(), dependencyAnalyzer.getMappedPersistableConfig(), deserializationContext.getTransaction());

				persisted = true;
			}
			catch (ChannelPrefixInUseException e)
			{
				deserializationContext.logDebug(" Error: Channel prefix in use");

				// deserialization must fail
				deserializationContext.setMustRollBack();

				// notify error
				String channelUsingPrefixPath = ManagerFactory.getChannelManager().getPath(deserializationContext.getSessionConfig(), e.getChannelUsingPrefixId(), true, deserializationContext.getTransaction());
				StringBuilder messageXml = new StringBuilder();
				messageXml.append("<error>");
				messageXml.append(" <prefix>");
				messageXml.append(e.getPrefix());
				messageXml.append(" </prefix>");
				messageXml.append(" <channelUsingPrefix>");
				messageXml.append("  <navigateScript>");
				messageXml.append(XmlUtil.encodeXml("window.parent.opener.document.location.href='../../../../"+PageConfig.PAGE_MAIN+"?"+PortalRequestParameters.PAGE_PARAMETER_CHANNELID+"=" + e.getChannelUsingPrefixId() + "';return false;"));
				messageXml.append("  </navigateScript>");
				messageXml.append("  <name>");
				messageXml.append(channelUsingPrefixPath);
				messageXml.append("  </name>");
				messageXml.append("  <id>");
				messageXml.append(e.getChannelUsingPrefixId());
				messageXml.append("  </id>");
				messageXml.append(" </channelUsingPrefix>");
				messageXml.append(" <conflictingChannel>");
				messageXml.append("  <name>");
				messageXml.append(deserializationContext.getCurrentChannelPath());
				messageXml.append("  </name>");
				messageXml.append("  <id>");
				messageXml.append(channelConfig.getId());
				messageXml.append("  </id>");
				messageXml.append(" </conflictingChannel>");
				messageXml.append("</error>");
				deserializationContext.sendProgressNotification(XmlUtil.getDocument(messageXml.toString()), "lumis/portal/serialization/style/ChannelPrefixConflict.xsl", Type.ERROR);

				// deserialize config with a fake prefix, in order to continue deserialization, that will rollback at the end
				channelConfig.setLocalGroupPrefix(PortalUtil.generateNewGuid());
				persisted = persistConfig(deserializationContext, config, firstPersistence);
			}
		}
		return persisted;
	}

	public void deleteConfig(DeserializationContext deserializationContext, String configId) throws PortalException
	{
		deserializationContext.logDebug("Deleting channel '" + configId + "'");
		ManagerFactory.getChannelManager().delete(deserializationContext.getSessionConfig(), configId, deserializationContext.getTransaction());
	}

	// SERIALIZATION

	public ChannelSerializableConfig serialize(SerializationContext serializationContext, String channelId) throws PortalException
	{
		// get config
		ChannelConfig channelConfig = ManagerFactory.getChannelManager().get(serializationContext.getSessionConfig(), channelId, serializationContext.getTransaction());
		String channelFullName = ManagerFactory.getChannelManager().getFriendlyPath(serializationContext.getSessionConfig(), channelId, serializationContext.getTransaction());
		serializationContext.setCurrentChannelPath(channelFullName);

		// get page list first, in order to get the page count and calculate the total itens for progress bar
		List<String> pageIds = ManagerFactory.getPageManager().getIdsByParentChannelId(serializationContext.getSessionConfig(), channelId, serializationContext.getTransaction());

		// initialize progress
		serializationContext.setItemProgressMax(getProgressSerializationItemCount() + pageIds.size());
		serializationContext.setItemProgressValue(0);
		serializationContext.updateProgressBar(1, 0, "STR_EXPORTING;" + channelFullName);

		// serialize config
		ManagerFactoryInternal.getSerializationManager2().serializeObject(channelConfig, serializationContext.getDirectory(), getConfigPath(serializationContext, channelConfig.getId()), configAlias);

		// analyze external dependencies
		analyzeExternalDependencies(serializationContext, channelConfig);

		// build structure object
		ChannelSerializableConfig channelSerializableConfig = createSerializableConfig(channelConfig);

		// serialize metaTags
		List<String> channelMetaTagIds = ManagerFactoryInternal.getMetaTagManager().getIdsByParentId(
				serializationContext.getSessionConfig(), channelId, serializationContext.getTransaction());
		for (String metaTagId: channelMetaTagIds)
		{
			MetaTagConfig metaTagConfig = ManagerFactoryInternal.getMetaTagManager().get(
					serializationContext.getSessionConfig(), metaTagId, serializationContext.getTransaction());
			channelSerializableConfig.getMetaTags().add(metaTagConfig);
		}

		// serialize service instances
		serializationContext.updateProgressBar(1, 1, "STR_EXPORTING_SERVICE_INSTANCES;" + channelFullName);
		for (String serviceInstanceId : ManagerFactory.getServiceInstanceManager().getIdsByChannelId(serializationContext.getSessionConfig(), channelId, serializationContext.getTransaction()))
			channelSerializableConfig.getServiceInstances().add(ManagerFactoryInternal.getServiceInstanceSerializationManager().serialize(serializationContext, serviceInstanceId));

		// serialize local groups
		serializationContext.updateProgressBar(1, 1, "STR_EXPORTING_LOCAL_GROUPS;" + channelFullName);
		for (GroupConfig groupConfig : ManagerFactory.getGroupManager().getByChannelId(serializationContext.getSessionConfig(), channelId, serializationContext.getTransaction()))
			channelSerializableConfig.getGroups().add(ManagerFactoryInternal.getGroupSerializationManager().serialize(serializationContext, groupConfig));

		// serialize local users
		serializationContext.updateProgressBar(1, 1, "STR_EXPORTING_LOCAL_USERS;" + channelFullName);
		for (UserConfig userConfig : ManagerFactory.getUserManager().getByChannelId(serializationContext.getSessionConfig(), channelId, serializationContext.getTransaction()))
			channelSerializableConfig.getUsers().add(ManagerFactoryInternal.getUserSerializationManager().serialize(serializationContext, userConfig));

		// serialize access control list
		serializationContext.updateProgressBar(1, 1, "STR_EXPORTING_ACL;" + channelFullName);
		channelSerializableConfig.setAccessControlList(ManagerFactoryInternal.getChannelAclSerializationManager().serialize(serializationContext, channelId));

		// serialize property bags
		serializationContext.updateProgressBar(1, 1, "STR_EXPORTING_PROPERTY_BAGS;" + channelFullName);
		ManagerFactoryInternal.getPropertyBagSerializationManager().serialize(serializationContext, channelConfig.getPropertyBagId());

		// serialize channel css
		serializationContext.updateProgressBar(1, 1, "STR_EXPORTING_CSS_LINKS;" + channelFullName);
		for (String cssId : ManagerFactory.getChannelManager().getCssIds(serializationContext.getSessionConfig(), channelId, serializationContext.getTransaction()))
		{
			channelSerializableConfig.getCsss().add(cssId);
			serializationContext.getReferencedCssIds().add(cssId);
		}

		// serialize channel template links
		serializationContext.updateProgressBar(1, 1, "STR_EXPORTING_CHANNEL_TEMPLATE_LINKS;" + channelFullName);
		channelSerializableConfig.getChannelTemplateLinks().addAll(ManagerFactoryInternal.getChannelTemplateSerializationManager().serializeLink(serializationContext, channelId));

		// notify progress
		serializationContext.updateProgressBar(1, 1, "STR_EXPORTING_PAGES;" + channelFullName);

		// serialize pages
		// backup original directory and serialization settings
		SerializationSettings channelSerializationSettings = serializationContext.getSerializationSettings().clone();
		SerializationSettings pageShallowSerializationSettings = channelSerializationSettings.clone();
		SerializationSettings pageDeepSerializationSettings = channelSerializationSettings.clone();
		IDirectory channelDirectory = serializationContext.getDirectory();

		// define channel name
		try
		{
			for (String pageId : pageIds)
			{
				// get values for structure object (shallow serialization).
				// An individual serialization settings (shallow) must be used, due to specific transient fields definition.
				serializationContext.setSerializationSettings(pageShallowSerializationSettings);
				channelSerializableConfig.getPages().add(ManagerFactoryInternal.getPageSerializationManager().serialize(serializationContext, pageId, false));

				// serialize page (deep serialization) into its own structure.
				// An individual serialization settings (deep) must be used, due to specific transient fields definition.
				serializationContext.setSerializationSettings(pageDeepSerializationSettings);
				serializationContext.setDirectory(new SubDirectory(channelDirectory, "pages/" + pageId));
				ManagerFactoryInternal.getSerializationManager2().serializePage(serializationContext, pageId, false);
			}
		}

		finally
		{
			// restore original directory and serialization settings
			serializationContext.setSerializationSettings(channelSerializationSettings);
			serializationContext.setDirectory(channelDirectory);
		}

		if(!serializationContext.isShallowSerialization())
		{
			// serialize subchannels
			for (String subChannelId : ManagerFactory.getChannelManager().getIdsByParentChannelId(serializationContext.getSessionConfig(), channelId, serializationContext.getTransaction()))
				channelSerializableConfig.getChannels().add(serialize(serializationContext, subChannelId));
		}

		return channelSerializableConfig;
	}

	private void analyzeExternalDependencies(SerializationContext serializationContext, ChannelConfig dependentChannelConfig) throws PortalException
	{
		if (dependentChannelConfig.getParentTemplateId() != null)
		{
			// if the channel template is outside the exporting channel tree, treat it
			// as an external dependency
			if (!serializationContext.getChannelIds().contains(dependentChannelConfig.getParentTemplateId()))
			{
				ChannelConfig dependableChannelTemplateConfig = ManagerFactory.getChannelManager().get(serializationContext.getSessionConfig(), dependentChannelConfig.getParentTemplateId(),
						serializationContext.getTransaction());

				Dependency dependency = new Dependency();
				dependency.setDependentNode(getSerializationMessageConfigNode(serializationContext, dependentChannelConfig));
				dependency.setDependableNode(getSerializationMessageConfigNode(serializationContext, dependableChannelTemplateConfig));

				serializationContext.getExternalDependencies().add(dependency);
			}
		}

	}

	private ChannelSerializableConfig createSerializableConfig(ChannelConfig channelConfig)
	{
		ChannelSerializableConfig channelSerializableConfig = new ChannelSerializableConfig();
		channelSerializableConfig.setId(channelConfig.getId());
		channelSerializableConfig.setName(channelConfig.getName());
		return channelSerializableConfig;
	}

	// GENERAL

	public void setupSerializationContext(BaseSerializationContext baseSerializationContext)
	{
		// define serialization aliases
		baseSerializationContext.getSerializationSettings().getClassAliases().put(ChannelSerializableConfig.class, "channel");
		baseSerializationContext.getSerializationSettings().getClassAliases().put(MetaTagConfig.class, "metaTag");
		baseSerializationContext.getSerializationSettings().getTransientFieldsForClass(ChannelConfig.class).add("inheritProtocol");
	}

	public SerializationMessageConfigNode getSerializationMessageConfigNode(BaseSerializationContext baseSerializationContext, ChannelConfig channelConfig) throws PortalException
	{
		ChannelSerializationMessageConfig channelSerializationMessageConfig = (channelConfig.isTemplate() ? new ChannelTemplateSerializationMessageConfig() : new ChannelSerializationMessageConfig());
		channelSerializationMessageConfig.setId(channelConfig.getId());
		channelSerializationMessageConfig.getNames().addAll(ManagerFactory.getChannelManager().getPathNames(baseSerializationContext.getSessionConfig(), channelConfig.getId(), true, baseSerializationContext.getTransaction()));

		SerializationMessageConfigNode serializationMessageConfigNode = new SerializationMessageConfigNode();
		serializationMessageConfigNode.setSerializationMessageConfig(channelSerializationMessageConfig);
		serializationMessageConfigNode.setNavigateScript("window.parent.opener.document.location.href='../../../../"+PageConfig.PAGE_MAIN+"?"+PortalRequestParameters.PAGE_PARAMETER_CHANNELID+"="+channelConfig.getId()+"';return false;");

		return serializationMessageConfigNode;
	}

	/**
	 * Get the full path for the serialization file.
	 * @param baseSerializationContext
	 * @param configId
	 * @return
	 */
	private String getConfigPath(BaseSerializationContext baseSerializationContext, String configId)
	{
		return "channels/" + configId + ".xml";
	}

	public int getProgressDeserializationItemCount()
	{
		return 11;
	}

	public int getProgressSerializationItemCount()
	{
		return 9;
	}
}
